//Copyright (c) 2006-2007 Microsoft Corporation.  All rights reserved
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Services.Description;
using System.Web.Services.Discovery;
using System.Web.Services.Protocols;
using System.Xml.Schema;

using Microsoft.CSharp;

using InvocationFramework.Common.Exceptions;
using InvocationFramework.Common.Resources;

namespace InvocationFramework.Adapters.WebServices
{
    /// <summary>
    /// </summary>
    /// <author>Rodolfo Finochietti 04/12/2006 20.31</author>
    internal class ProxyBuilder
    {
        #region Constants

        private const string ProxyNameSpace = "InvocationFramework.Adapters.WebServices.DynamicProxy";

        #endregion

        #region Private Vars

        private Uri _wsdlLocation;

        private DiscoveryProtocol _protocolName = DiscoveryProtocol.Soap;

        private Type _serviceContract;

        #endregion

        #region Constructors

        public ProxyBuilder( string wsdlLocation, DiscoveryProtocol protocolName, Type serviceContract ) : this( new Uri( wsdlLocation ), protocolName, serviceContract ) { }

        public ProxyBuilder( Uri wsdlLocation, DiscoveryProtocol protocolName, Type serviceContract )
        {
            _wsdlLocation = wsdlLocation;
            _protocolName = protocolName;
            _serviceContract = serviceContract;
        }

        #endregion

        #region Public Methods

        public Assembly Build( )
        {
            if ( _wsdlLocation == null )
                throw new ArgumentNullException( "WsdlLocation", String.Format( CultureInfo.InvariantCulture, Texts.ProxyBuilderArgumentNull, "WsdlLocation" ) );

            ServiceDescriptionImporter importer = new ServiceDescriptionImporter( );
            importer.ProtocolName = _protocolName.ToString( );

            //Get WSDL
            DiscoverService( importer );

            //Proxy source code generation
            Assembly ret;

            using ( CSharpCodeProvider codeProvider = new CSharpCodeProvider( ) )
            {
                ret = GenerateProxyAssembly( codeProvider, GenerateProxySourceCode( importer, codeProvider ) );
            }

            //Clean up resources
            importer = null;

            return ret;
        }

        #endregion

        #region Private Methods

        [System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Performance", "CA1800:DoNotCastUnnecessarily" )]
        private void DiscoverService( ServiceDescriptionImporter importer )
        {
            using ( DiscoveryClientProtocol discoClient = new DiscoveryClientProtocol( ) )
            {
                discoClient.DiscoverAny( _wsdlLocation.ToString( ) );
                discoClient.ResolveAll( );

                foreach ( object value in discoClient.Documents.Values )
                {
                    if ( value is ServiceDescription )
                        importer.AddServiceDescription( ( ServiceDescription )value, null, null );

                    if ( value is XmlSchema )
                        importer.Schemas.Add( ( XmlSchema )value );
                }
            }
        }

        private string GenerateProxySourceCode( ServiceDescriptionImporter importer, CSharpCodeProvider codeProvider )
        {
            CodeNamespace mainNamespace = new CodeNamespace( ProxyNameSpace );
            importer.Import( mainNamespace, null );

            AddImports( mainNamespace );
            
            //If service contract was specified add it to proxy stub
            if ( _serviceContract != null )
            {
                CodeTypeDeclaration[] temp = new CodeTypeDeclaration[ mainNamespace.Types.Count ];
                mainNamespace.Types.CopyTo( temp, 0 );
                foreach ( CodeTypeDeclaration declarationType in temp )
                {
                    if ( ( declarationType.BaseTypes.Count > 0 ) && ( declarationType.BaseTypes[ 0 ].BaseType == typeof( SoapHttpClientProtocol ).FullName ) )
                        declarationType.BaseTypes.Add( _serviceContract );
                    else
                        mainNamespace.Types.Remove( declarationType );
                }

                //Clean up resources
                temp = null;
            }

            //Proxy source code generation
            StringBuilder sourceCode = new StringBuilder( );
            string ret;

            using ( StringWriter sw = new StringWriter( sourceCode, CultureInfo.CurrentCulture ) )
            {
                codeProvider.GenerateCodeFromNamespace( mainNamespace, sw, null );
                ret = sourceCode.ToString( );
            }

            //Clean up resources
            mainNamespace = null;
            sourceCode = null;

            return ret;
        }

        private void AddImports( CodeNamespace codeNamespace )
        {
            if ( _serviceContract != null )
            {
                MethodInfo[] methods = _serviceContract.GetMethods( );
                foreach ( MethodInfo method in methods )
                {
                    AddImport( codeNamespace, method.ReturnType );

                    foreach ( ParameterInfo parameter in method.GetParameters( ) )
                        AddImport( codeNamespace, parameter.ParameterType );
                }
            }
        }
        
        private static void AddImport( CodeNamespace codeNamespace, Type type )
        {
            if ( !type.IsPrimitive )
                codeNamespace.Imports.Add( new CodeNamespaceImport( type.Namespace ) );
        }
        
        [System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Reliability","CA2000:DisposeObjectsBeforeLosingScope" )]
        private Assembly GenerateProxyAssembly( CSharpCodeProvider csharpCodeProvider, string proxyCode )
        {
            //Assembly compilation
            string location = "";

            if ( HttpContext.Current != null )
            {
                location = HttpContext.Current.Server.MapPath( "." );
                location += @"\bin\";
            }

            CompilerParameters parameters = new CompilerParameters( );

            parameters.ReferencedAssemblies.Add( "System.dll" );
            parameters.ReferencedAssemblies.Add( "System.Data.dll" );
            parameters.ReferencedAssemblies.Add( "System.Xml.dll" );
            parameters.ReferencedAssemblies.Add( "System.Web.dll" );
            parameters.ReferencedAssemblies.Add( "System.Web.Services.dll" );
            parameters.ReferencedAssemblies.Add( Assembly.GetExecutingAssembly( ).Location );
            if ( _serviceContract != null )
                GetReferencedAssemblies( _serviceContract.Assembly, parameters );

            parameters.GenerateExecutable = false;
            parameters.GenerateInMemory = false;
            parameters.IncludeDebugInformation = false;
            parameters.TempFiles = new TempFileCollection( ProxyAssemblyCache.GetCachePath( ) );
            
            CompilerResults cr = csharpCodeProvider.CompileAssemblyFromSource( parameters, proxyCode );

            if ( cr.Errors.Count > 0 )
            {
                StringBuilder sb = new StringBuilder( );
                for ( int i = 0; i < cr.Errors.Count; i++ )
                {
                    sb.Append( Environment.NewLine );
                    sb.Append( "'" );
                    sb.Append( String.Format( CultureInfo.InvariantCulture, "{0} - {1}", cr.Errors[ i ].ErrorNumber, cr.Errors[ i ].ErrorText ) );
                    sb.Append( "'" );
                }

                throw new InvocationFrameworkException( String.Format( CultureInfo.InvariantCulture, Texts.ProxyBuilderBuildProxyFailed, cr.Errors.Count, sb.ToString( ) ) );
            }

            Assembly ret = cr.CompiledAssembly;

            //Clean up resources
            cr = null;
            parameters = null;

            return ret;
        }

        private static void GetReferencedAssemblies( Assembly assembly, CompilerParameters parameters )
        {
            if ( !parameters.ReferencedAssemblies.Contains( assembly.Location ) )
            {
                string location = Path.GetFileName( assembly.Location );
                if ( !parameters.ReferencedAssemblies.Contains( location ) )
                {
                    parameters.ReferencedAssemblies.Add( assembly.Location );
                    foreach ( AssemblyName referencedAssembly in assembly.GetReferencedAssemblies( ) )
                        GetReferencedAssemblies( Assembly.Load( referencedAssembly.FullName ), parameters );
                }
            }
        }

        #endregion
    }
}